﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Web.Security;
using System.Web.Services;
using System.Xml.Serialization;
using CCS;
using Microsoft.Samples.ServiceHosting.StorageClient;

namespace CCSService_WebRole
{
    /// <summary>
    /// Summary description for CCS
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    // [System.Web.Script.Services.ScriptService]
    public class CCS : WebService
    {
        #region base CCS methods

        [WebMethod]
        public bool RegisterUser(string lastname, string firstname, string address, string city, string state, string zip, string phone, string email, string devToken, string productName, string uniqueMachineID, string licenseLevel)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            Guid devTokenGuid = new Guid(devToken);
            CCSUser user = Procedures.AddUserInfo(accountInfo, tableStorage, lastname, firstname, address, city, state, zip, phone, email, devTokenGuid, productName, licenseLevel, uniqueMachineID);
            bool retval = false;
            if (user != null)
            {
                retval = Procedures.UpdateDataStoreRegistration(tableStorage, accountInfo, devTokenGuid, productName, uniqueMachineID, true, licenseLevel);
            }

            return retval;
        }

        [WebMethod]
        public string GetLicense(string product, string clientIdentity, string licenseLevel, string invoice, string devToken, string clientSerialId)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            var licenseRequest = new LicenseRequest { Product = product.ToString(), ClientIdentity = clientIdentity, LicenseLevel = licenseLevel.ToString() };
            clientIdentity = licenseRequest.ClientIdentity;

            Guid devTokenGuid = new Guid(devToken);

            CCSProduct ccsProduct = Procedures.Get<CCSProduct>(new CCSProductServiceContext(accountInfo), new Dictionary<string, object> { { "DevToken", new Guid(devToken) }, { "ProductName", product } }).Single();
            CCSLicenseLevelPrice ccsLicenseLevel = Procedures.Get<CCSLicenseLevelPrice>(new CCSLicenseLevelPriceServiceContext(accountInfo), new Dictionary<string, object> { { "ProductID", ccsProduct.ID }, { "LicenseLevel", licenseLevel } }).Single();

            Guid licenseLevelGuid = ccsLicenseLevel.LicenseID;
            Procedures.StoreLicenseTransaction(tableStorage, invoice, clientIdentity, ccsProduct.ID, ccsLicenseLevel.LicenseID, devTokenGuid, clientSerialId);
            var server = new LicenseServer();

            var output = new StringWriter(new StringBuilder());

            if (Procedures.VerifyLicenseAvailableInDB(clientIdentity, ref licenseLevelGuid, ccsProduct.ID, invoice, devTokenGuid))
            {
                LicenseResponse response = new LicenseResponse();
                response = server.CreateLicenseReponse(licenseRequest);
                licenseRequest.LicenseLevel = licenseLevel.ToString();
                Procedures.StoreAuthLicense(tableStorage, accountInfo, invoice, response.GetAuthenticatedLicenseAsString());
                Procedures.UpdateDataStoreRegistrationKey(tableStorage, accountInfo, devTokenGuid, product, response.GetAuthenticatedLicenseAsString(), licenseLevel);
                var xs = new XmlSerializer(typeof(LicenseResponse));
                xs.Serialize(output, response);
                return output.ToString();
            }

            return null;
        }

        [WebMethod]
        public bool SubmitIssue(string productName, string licenseLevel, string emailContact, string firstName, string lastName, string description, string devID, string uniqueMachineID)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            return Procedures.SubmitIssue(tableStorage, accountInfo, productName, licenseLevel, emailContact, firstName, lastName, description, new Guid(devID), uniqueMachineID) != null;
        }

        [WebMethod]
        public bool SubmitException(string message, string source, string stackTrace, string methodName, string productName, string devToken, string uniqueMachineID, string licenseLevel)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            return Procedures.SubmitException(tableStorage, message, source, stackTrace, methodName, productName, new Guid(devToken), uniqueMachineID, licenseLevel) != null;
        }

        [WebMethod]
        public ClientAppConfig GetClientAppConfig(string productName, string devToken, string uniqueMachineID, DateTime startDateTime)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();

            CCSProductServiceContext productServiceContext = new CCSProductServiceContext(accountInfo);
            var product = productServiceContext.CCSProducts.Where(p => p.ProductName == productName).FirstOrDefault();

            CCSProductConfigSettingsServiceContext productConfigSettingsServiceContext = new CCSProductConfigSettingsServiceContext(accountInfo);
            var productConfigSettings = productConfigSettingsServiceContext.CCSProductConfigSettings.Where(p => p.ProductID == product.ID).FirstOrDefault();

            CCSLicenseLevelPriceServiceContext licenseLevelPriceServiceContext = new CCSLicenseLevelPriceServiceContext(accountInfo);
            var licenseLevelPrice = licenseLevelPriceServiceContext.CCSLicenseLevelPrices.Where(l => l.ProductID == product.ID).FirstOrDefault();

            Guid devTokenGuid = new Guid(devToken);

            CCSProductDataStore productDataStore = Procedures.GetProductDataStoreSettings(accountInfo, new Guid(devToken), productName, uniqueMachineID);
            if (productDataStore == null)
            {
                productDataStore = Procedures.FindOrCreateProductDataStore(tableStorage, accountInfo, devTokenGuid, product.ID, licenseLevelPrice.LicenseID, true, uniqueMachineID);
            }

            CCSPaymentSettingsServiceContext paymentSettingsServiceContext = new CCSPaymentSettingsServiceContext(accountInfo);
            var paymentSettings = paymentSettingsServiceContext.CCSPaymentSettings.Where(p => p.DevToken == devTokenGuid).FirstOrDefault();

            string serverPublicKey;
            LicenseServer server = new LicenseServer();
            server.CreateNewProduct(productName, out serverPublicKey);

            LicenseLevelsPrice[] licenses = Procedures.Get<CCSLicenseLevelPrice>(new CCSLicenseLevelPriceServiceContext(accountInfo), new Dictionary<string, object> { { "ProductID", product.ID }, { "DevToken", devTokenGuid } }).Select(l => new LicenseLevelsPrice(l)).ToArray();

            ClientAppConfig cag = new ClientAppConfig { DevToken = devTokenGuid.ToString(), AbortTimerLength = productConfigSettings.AbortTimerLength, ActivateNowMaxNumPoll = productConfigSettings.ActivateNowMaxNumPoll, BuyNowMaxNumPoll = productConfigSettings.BuyNowMaxNumPoll, DataStoreSettings = new DataStore(productDataStore, accountInfo, true), LicenseLevelPrices = licenses, PaymentSettings = new PaymentSettings(paymentSettings), PollingTimerLength = productConfigSettings.PollingTimerLength, ProductName = productName, ServerPublicKey = serverPublicKey, TrialLengthDays = productConfigSettings.TrialLengthDays };

            return cag;
        }

        #endregion

        [WebMethod]
        public Guid[] GetDevToken(Guid userKey, CCSCredentials credentials)
        {
            _ValidateCredentials(credentials);

            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            CCSDevTokenServiceContext devTokenServiceContext = new CCSDevTokenServiceContext(accountInfo);
            var devTokens = devTokenServiceContext.CCSDevTokens.Where(d => d.UserName == userKey).ToArray();
            Guid[] retval = new Guid[devTokens.Length];
            int ctr = 0;
            foreach (var devToken in devTokens)
            {
                retval[ctr] = devToken.DeveloperToken;
                ++ctr;
            }
            return retval;
        }

        [WebMethod]
        public Guid GetProviderUserKey(string userName, CCSCredentials credentials)
        {
            _ValidateCredentials(credentials);
            return new Guid(Membership.GetUser(userName).ProviderUserKey.ToString());
        }

        #region Membership pass-through methods

        [WebMethod]
        public MembershipUser CreateUser(string userName, string password, CCSPaymentSettings paymentSettings)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();

            MembershipUser retval = default(MembershipUser);

            CCSDevToken devtoken = new CCSDevToken(CCSDevToken.TableName, Extensions.DefaultRowKey().ToString());
            devtoken.Timestamp = DateTime.UtcNow;
            bool saved = devtoken.Save(tableStorage, CCSDevToken.TableName, CCSDevToken.TableName);
            if (saved)
            {
                retval = Membership.CreateUser(userName, password, paymentSettings.PayPal_AccountEmail);
                devtoken.DeveloperToken = Guid.NewGuid();
                devtoken.UserName = new Guid(retval.ProviderUserKey.ToString());
                bool updated = devtoken.Update(tableStorage, CCSDevToken.TableName);
                if (!updated)
                {
                    throw new Exception(string.Format("Failed to create a developer token for user name {0}", retval.UserName));
                }

                Procedures.CreatePaymentSettings(tableStorage, devtoken.DeveloperToken, paymentSettings.PayPal_AccountEmail, paymentSettings.PayPal_ItemNumber, paymentSettings.PayPal_Quantity, paymentSettings.PayPal_currencyCode, paymentSettings.PayPal_notifyURL, paymentSettings.PayPal_requireShippingAddress, paymentSettings.PayPal_includeNote, paymentSettings.PayPal_logoURL, paymentSettings.PayPal_tag, paymentSettings.PayPal_noteTitle, paymentSettings.PayPal_successUrl, paymentSettings.PayPal_cancelURL, paymentSettings.Payment_URL);
            }

            return retval;
        }

        [WebMethod]
        public bool ValidateUser(CCSCredentials credentials)
        {
            return Membership.ValidateUser(credentials.UserName, credentials.Password);
        }

        [WebMethod]
        public MembershipUser[] GetAllUsers(CCSCredentials credentials)
        {
            _ValidateCredentials(credentials);

            var users = Membership.GetAllUsers();
            return users.Cast<MembershipUser>().ToArray();
        }

        [WebMethod]
        public bool DeleteUserByEmail(CCSCredentials credentials, string email)
        {
            _ValidateCredentials(credentials);

            var user = GetAllUsers(credentials).Where(u => u.Email == email).FirstOrDefault();
            return Membership.DeleteUser(user.UserName);
        }

        #endregion

        [WebMethod]
        public bool AddApplication(Guid developerToken, string applicationName, string version, DateTime releaseDate, int licensesPerInvoice, int trialLengthInDays)
        {
            bool retval = false;
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            CCSProduct product = Procedures.InsertNewProduct(tableStorage, applicationName, releaseDate, version, licensesPerInvoice, 10, 10, trialLengthInDays, 30000, 30000, developerToken);
            retval = product != null;
            return retval;
        }

        [WebMethod]
        public DeveloperApplication[] GetProducts(Guid developerToken)
        {
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            CCSProductServiceContext productServiceContext = new CCSProductServiceContext(accountInfo);
            var products = productServiceContext.CCSProducts.Where(p => p.DevToken == developerToken);
            List<DeveloperApplication> applications = new List<DeveloperApplication>();
            foreach (var product in products)
            {
                Guid productId = product.ID;

                CCSProductConfigSettingsServiceContext productConfigSettingsServiceContext = new CCSProductConfigSettingsServiceContext(accountInfo);
                var productConfigSettings = productConfigSettingsServiceContext.CCSProductConfigSettings.Where(p => p.ProductID == productId).FirstOrDefault();

                applications.Add(new DeveloperApplication { ID = productId, Name = product.ProductName, Version = product.ProductVersion.Split(new[] { '.' }).Select(v => Convert.ToInt32(v)).ToArray(), LicensesPerInvoice = product.LicensesPerInvoice, TrialLengthInDays = productConfigSettings.TrialLengthDays, ReleaseDate = product.ProductReleaseDate, Activations = Procedures.Get<CCSLicenseTransaction>(new CCSLicenseTransactionServiceContext(accountInfo), new Dictionary<string, object> { { "ProductID", productId } }).ToArray(), Exceptions = Procedures.Get<CCSException>(new CCSExceptionServiceContext(accountInfo), new Dictionary<string, object> { { "ProductID", productId } }).ToArray(), Issues = Procedures.Get<CCSIssue>(new CCSIssueServiceContext(accountInfo), new Dictionary<string, object> { { "IssueProductID", productId } }).ToArray(), Registrations = Procedures.Get<CCSProductDataStore>(new CCSProductDataStoreServiceContext(accountInfo), new Dictionary<string, object> { { "ProductID", productId } }).ToArray() });
            }
            return applications.ToArray();
        }

        [WebMethod]
        public MembershipUser[] GetTeamMembers(CCSCredentials credentials, Guid devToken)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            CCSDevTokenServiceContext devTokenServiceContext = new CCSDevTokenServiceContext(accountInfo);
            var devtokens = devTokenServiceContext.CCSDevTokens.Where(d => d.DeveloperToken == devToken).ToArray();
            var users = GetAllUsers(credentials);
            MembershipUser[] members = new MembershipUser[devtokens.Length];
            int ctr = 0;
            foreach (var teamDevtoken in devtokens)
            {
                Guid userName = teamDevtoken.UserName;
                members[ctr] = users.Where(u => new Guid(u.ProviderUserKey.ToString()) == userName).FirstOrDefault();
                ++ctr;
            }
            return members;
        }

        [WebMethod]
        public bool AddTeamMember(Guid devToken, string email)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            var membershipUser = Membership.FindUsersByEmail(email).Cast<MembershipUser>().FirstOrDefault();
            CCSDevToken devtoken = new CCSDevToken(CCSDevToken.TableName, Extensions.DefaultRowKey().ToString());
            devtoken.Timestamp = DateTime.UtcNow;
            devtoken.DeveloperToken = devToken;
            devtoken.UserName = new Guid(membershipUser.ProviderUserKey.ToString());
            bool saved = devtoken.Save(tableStorage, CCSDevToken.TableName, CCSDevToken.TableName);
            if (!saved)
            {
                throw new Exception(string.Format("Failed to create a developer token for user name {0}", membershipUser.UserName));
            }
            return true;
        }

        [WebMethod]
        public bool DeleteProduct(Guid productId, CCSCredentials credentials)
        {
            _ValidateCredentials(credentials);

            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();

            CCSProductServiceContext productServiceContext = new CCSProductServiceContext(accountInfo);
            var product = productServiceContext.CCSProducts.Where(p => p.ID == productId).FirstOrDefault();
            if (product != null)
            {
                return product.Delete(tableStorage, CCSProduct.TableName);
            }
            return false;
        }

        [WebMethod]
        public bool UpdateProduct(CCSCredentials credentials, DeveloperApplication application)
        {
            _ValidateCredentials(credentials);
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();
            string version = string.Join(".", application.Version.Select(v => v.ToString()).ToArray());
            return Procedures.UpdateProductSettings(tableStorage, accountInfo, application.Name, DateTime.Now, version, application.LicensesPerInvoice, application.ID, 10000, 10000, application.TrialLengthInDays, 10000, 10000);
            ;
        }

#if DEBUG
        [WebMethod]
        public void CreateTestData(string notifyUrl, string paymentUrl)
        {
            TableStorage tableStorage = StorageClientHelper.GetTableStorage();
            StorageAccountInfo accountInfo = StorageClientHelper.GetAccountInfo();

            // default devtoken
            Guid devToken = new Guid("D3DB356D-920B-4EAA-9104-CFE06B1A522D");

            // Create a default product if we don't have one
            CCSProductServiceContext productService = new CCSProductServiceContext(accountInfo);
            var sampleApp = productService.CCSProducts.Where(p => p.ProductName == "SampleApp");
            if (sampleApp.FirstOrDefault() == null)
            {
                Procedures.InsertNewProduct(tableStorage, "SampleApp", DateTime.Now.AddYears(1), 1.ToString(), 1, 1000, 1000, 30, 30000, 1000, devToken);
            }

            // Create default payment settings if we don't have them
            CCSPaymentSettingsServiceContext paymentSettingsService = new CCSPaymentSettingsServiceContext(accountInfo);
            var paymentSettings = paymentSettingsService.CCSPaymentSettings.Where(p => p.DevToken == devToken);
            if (paymentSettings.FirstOrDefault() == null)
            {
                Procedures.CreatePaymentSettings(tableStorage, devToken, "test@domain.com", 1.ToString(), 1, "USD", notifyUrl, false, false, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, paymentUrl);
            }
        }
#endif

        private void _ValidateCredentials(CCSCredentials credentials)
        {
            if (!Membership.ValidateUser(credentials.UserName, credentials.Password))
            {
                throw new MembershipValidationException("Unable to verify user name and password combination.");
            }
        }
    }

    [Serializable]
    public class DeveloperApplication
    {
        public Guid ID { get; set; }
        public string Name { get; set; }
        public int[] Version { get; set; }
        public int LicensesPerInvoice { get; set; }
        public int TrialLengthInDays { get; set; }
        public DateTime ReleaseDate { get; set; }
        public CCSLicenseTransaction[] Activations { get; set; }
        public CCSIssue[] Issues { get; set; }
        public CCSException[] Exceptions { get; set; }
        public CCSProductDataStore[] Registrations { get; set; }
    }

    [Serializable]
    public class MembershipValidationException : Exception
    {
        public MembershipValidationException()
        {
        }

        public MembershipValidationException(string message)
            : base(message)
        {
        }

        public MembershipValidationException(string message, Exception inner)
            : base(message, inner)
        {
        }

        protected MembershipValidationException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    public class CCSCredentials
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    #region Data Classes (and contracts)

    public class LicenseLevelsPrice
    {
        public string Description { get; set; }

        public Guid LicenseLevelID { get; set; }

        public bool IsTrialLicensekey { get; set; }

        public string LicenseLevel { get; set; }

        public double LicenseLevelPrice { get; set; }

        public LicenseLevelsPrice(CCSLicenseLevelPrice licenseLevelPrice)
        {
            this.Description = licenseLevelPrice.Description;
            this.LicenseLevelID = licenseLevelPrice.LicenseID;
            this.IsTrialLicensekey = licenseLevelPrice.TrialLicenseKey;
            this.LicenseLevel = licenseLevelPrice.LicenseLevel;
            this.LicenseLevelPrice = licenseLevelPrice.LicenseLevelPrice;
        }

        public LicenseLevelsPrice()
        {
        }
    }

    public class PaymentSettings
    {
        public string PayPalAccountEmail { get; set; }

        public string PayPalItemNumber { get; set; }

        public int PayPalQuantity { get; set; }

        public string PayPalCurrencyCode { get; set; }

        public string PayPalNotifyURL { get; set; }

        public Boolean PayPalRequireShippingAddress { get; set; }

        public Boolean PayPalIncludeNote { get; set; }

        public string PayPalLogoURL { get; set; }

        public string PayPalTag { get; set; }

        public string PayPalNoteTitle { get; set; }

        public string PayPalSuccessURL { get; set; }

        public string PayPalCancelURL { get; set; }

        public string PaymentURL { get; set; }

        public PaymentSettings(CCSPaymentSettings paymentSettings)
        {
            PaymentURL = paymentSettings.Payment_URL;
            PayPalAccountEmail = paymentSettings.PayPal_AccountEmail;
            PayPalCancelURL = paymentSettings.PayPal_cancelURL;
            PayPalCurrencyCode = paymentSettings.PayPal_currencyCode;
            PayPalIncludeNote = paymentSettings.PayPal_includeNote;
            PayPalItemNumber = paymentSettings.PayPal_ItemNumber;
            PayPalLogoURL = paymentSettings.PayPal_logoURL;
            PayPalNoteTitle = paymentSettings.PayPal_noteTitle;
            PayPalNotifyURL = paymentSettings.PayPal_notifyURL;
            PayPalQuantity = paymentSettings.PayPal_Quantity;
            PayPalRequireShippingAddress = paymentSettings.PayPal_requireShippingAddress;
            PayPalSuccessURL = paymentSettings.PayPal_successUrl;
            PayPalTag = paymentSettings.PayPal_tag;
        }

        public PaymentSettings()
        {
        }
    }

    public class DataStore
    {
        public string RegistrationKey { get; set; }

        public DateTime DateTime { get; set; }

        public string LicenseLevel { get; set; }

        public int NumberOfTrialDaysElapsed { get; set; }

        public DateTime StartDate { get; set; }

        public Boolean UserInfoRegistered { get; set; }

        public String UniqueMachineID { get; set; }

        public Boolean RefreshDataStore { get; set; }

        public DataStore(CCSProductDataStore productDataStore, StorageAccountInfo accountInfo, bool refreshDataStore)
        {
            this.RegistrationKey = productDataStore.RegistrationKey;
            this.DateTime = productDataStore.DateTime;
            CCSLicenseLevelPriceServiceContext licenseLevelPriceServiceContext = new CCSLicenseLevelPriceServiceContext(accountInfo);
            var licenseLevelPrice = licenseLevelPriceServiceContext.CCSLicenseLevelPrices.Where(l => l.ProductID == productDataStore.ProductID).FirstOrDefault();
            this.LicenseLevel = licenseLevelPrice.LicenseLevel;
            this.NumberOfTrialDaysElapsed = productDataStore.NumberOfTrialDaysElapsed;
            this.StartDate = productDataStore.StartDate;
            this.UserInfoRegistered = productDataStore.UserInfoRegistered;
            this.UniqueMachineID = productDataStore.UniqueMachineID;
            this.RefreshDataStore = refreshDataStore;
        }

        public DataStore()
        {
        }
    }

    public class ClientAppConfig
    {
        public DataStore DataStoreSettings { get; set; }

        public int BuyNowMaxNumPoll { get; set; }

        public int ActivateNowMaxNumPoll { get; set; }

        public int TrialLengthDays { get; set; }

        public int AbortTimerLength { get; set; }

        public int PollingTimerLength { get; set; }

        public string DevToken { get; set; }

        public string ServerPublicKey { get; set; }

        public LicenseLevelsPrice[] LicenseLevelPrices { get; set; }

        public PaymentSettings PaymentSettings { get; set; }

        public string ProductName { get; set; }
    }

    #endregion
}